/******************************************************************************
 * @brief    命令管理器
 * Change Logs:
 * Date           Author         Email                   Notes
 * 2023-06-06      子恒     3393018959@qq.com      Initial version
 *
 * 2023-09-04      子恒     3393018959@qq.com      1、增加内置HELP命令选配
 *                                                 2、增加自定义子串分割符
 *
 * 2023-09-06      子恒     3393018959@qq.com      1、优化b_cmd_t类型为常量
 *                                                 2、优化b_cmd_str_t->*pstr为常量指针
 *
 * 2023-12-01      子恒     3393018959@qq.com       1、优化接口
 *                                                  2、避免使用spritf函数，以便8位机通用
 *
 * 2024-04-09      子恒     3393018959@qq.com       1、简化移植方式<依赖ringbuffer>
 *                                                  2、增加字符串匹配表，用于匹配非命令格式字符串，可裁剪
 ******************************************************************************/

// todo:移植文档
// todo:优化指针变量安全性
#include "cmd.h"

static b_cmd_mgr_t b_cmd_mgr;
static uint8_t line_index = 0;
static ring_buf_t cmds_ring = {NULL};
static uint8_t cmd_ring_buf[B_CMD_RING_MAX];
static b_cmd_str_t cmd_str_end = {0};

#if (B_CMD_HELP_EN != 0)
static uint8_t cmd_maxlen = 0;
#endif

void cmd_mgr_init(const b_cmd_t *cmds, const b_cmd_str_t *strs, void *putc_cb)
{
#if (B_CMD_HELP_EN != 0)
    const b_cmd_t *pcmds = cmds;
	const b_cmd_str_t *pstrs = strs;
    uint8_t len = 0;
    while (pcmds->head_name != NULL)
    {
        len = strlen(pcmds->head_name);
        if (len > cmd_maxlen)
        {
            cmd_maxlen = len;
        }
        pcmds++;
    }
	while (pstrs->pstr != NULL)
    {
        len = strlen(pstrs->pstr);
        if (len > cmd_maxlen)
        {
            cmd_maxlen = len;
        }
        pstrs++;
    }
#endif

    cmd_str_end.pstr = B_CMD_END_STR;
    cmd_str_end._flag = 0xff;

    b_cmd_mgr.cmds = (b_cmd_t *)cmds;
    b_cmd_mgr.strs = (b_cmd_str_t *)strs;
    b_cmd_mgr.pf_outchar = (b_cmd_out_char_cb_t *)putc_cb;
    ring_buf_init(&cmds_ring, cmd_ring_buf, B_CMD_RING_MAX);
    memset(b_cmd_mgr.line, 0, sizeof(b_cmd_mgr.line));
}

uint8_t cmd_ring_puts(uint8_t *pch,uint8_t len)
{
    if (cmds_ring.buf != NULL){
        if(ring_buf_put(&cmds_ring, pch, len) == len){
			return B_CMD_RET_PASS;
		}
	}
	return B_CMD_RET_FAIL;
}

static uint8_t cmd_ring_getc(uint8_t *ch)
{
    return ring_buf_get(&cmds_ring, ch, 1);
}

void b_cmd_put_char(const char ch)
{
    if (b_cmd_mgr.pf_outchar != NULL)
    {
        b_cmd_mgr.pf_outchar(ch);
    }
}

void b_cmd_put_str(const char *const str)
{
    const char *ptemp = str;
    if (b_cmd_mgr.pf_outchar != NULL)
    {
        while (*ptemp)
        {
            b_cmd_mgr.pf_outchar(*ptemp);
            ptemp++;
        }
    }
}

static const b_cmd_t *b_cmd_get_next_cmd(const b_cmd_t *const addr)
{
    const b_cmd_t *ptr = (const b_cmd_t *)((char *)addr + sizeof(b_cmd_t));
    if (ptr->head_name != NULL)
    {
        return ptr;
    }
    return NULL;
}

static const b_cmd_str_t *b_cmd_str_get_next_cmd(const b_cmd_str_t *const addr)
{
    const b_cmd_str_t *ptr = (const b_cmd_str_t *)((char *)addr + sizeof(b_cmd_str_t));
    if (ptr->pstr != NULL)
    {
        return ptr;
    }
    return NULL;
}

static uint8_t b_cmd_parse_str(b_cmd_str_t *str_t, uint8_t ch)
{
    if (str_t->pstr[str_t->_i] == ch)
    {
        str_t->_i++;
        if (str_t->pstr[str_t->_i] == '\0')
        {
            str_t->_i = 0;
            if (str_t->_flag != 0xff)
            {
                line_index = 0;
            }
            return 1;
        }
    }
    else
    {
        str_t->_i = (str_t->pstr[0] == ch) ? 1 : 0;
    }
    return 0;
}

#if (B_CMD_VAR_PRINT_EN != 0)

static void cmd_print_var_cb(uint8_t argc, char *argv[])
{
    uint8_t i = 0;
	const char *tab="0123456789abcdef";
    for (i = 0; i < argc; i++)
    {
        b_cmd_put_str("argv[");
        b_cmd_put_char(tab[((i & 0xf0) >> 4)]);
        b_cmd_put_char(tab[((i & 0x0f) >> 0)]);
        b_cmd_put_str("] = ");
        b_cmd_put_str(argv[i]);
        b_cmd_put_str("\r\n");
    }
}
#endif

#if (B_CMD_HELP_EN != 0)

static uint8_t cb_cmd_help(uint8_t argc, char *argv[])
{
    char buf[128];
    const b_cmd_t *pcmds;
	const b_cmd_str_t *pstrs;
    uint8_t i = 0;
    pcmds = b_cmd_mgr.cmds;
	pstrs = b_cmd_mgr.strs;

	b_cmd_put_str("\r\n");
    b_cmd_put_str(B_CMD_HELP_PRINT);
	b_cmd_put_str("----------------------------\r\n");
    if (pcmds == NULL)
    {
        b_cmd_put_str("warning cmds is NULL!\r\n");
        return B_CMD_RET_PASS;
    }
	
	b_cmd_put_str("\r\n-----cmds-----\r\n\r\n");
	
    while (pcmds->head_name != NULL)
    {
        b_cmd_put_str(pcmds->head_name);
        for (i = 0; i < cmd_maxlen - strlen(pcmds->head_name) + 5; i++)
        {
            b_cmd_put_str(" ");
        }
        b_cmd_put_str("[");
        if (pcmds->help_str != NULL)
        {
            b_cmd_put_str(pcmds->help_str);
        }
        else
        {
            b_cmd_put_str("NULL");
        }
        b_cmd_put_str("]\r\n");
        pcmds++;
    }
	if(pstrs == NULL) return B_CMD_RET_PASS;
	b_cmd_put_str("\r\n-----strs-----\r\n\r\n");
	while (pstrs->pstr != NULL)
    {
        b_cmd_put_str(pstrs->pstr);
		for (i = 0; i < cmd_maxlen - strlen(pstrs->pstr) + 5; i++)
        {
            b_cmd_put_str(" ");
        }
		b_cmd_put_str("[");
        if (pstrs->help_str != NULL)
        {
            b_cmd_put_str(pstrs->help_str);
        }
        else
        {
            b_cmd_put_str("NULL");
        }
		b_cmd_put_str("]\r\n");
        pstrs++;
    }
	
	b_cmd_put_str("\r\n--------------\r\n\r\n");
    return B_CMD_RET_PASS;
}
static const b_cmd_t cmds_help = {"HELP", cb_cmd_help, NULL};

#endif

static uint8_t char_in_string(uint8_t *str, uint8_t ch)
{
    while (*str)
    {
        if (*str == ch)
            return 1;
        str++;
    }
    return 0;
}

void b_cmd_parse_char(uint8_t ch)
{

    uint8_t argc = 0;
    char *argv[B_CMD_ARGS_NUM_MAX + 1] = {0};
    char **pagv = NULL;
    const b_cmd_t *cmds_ptr = b_cmd_mgr.cmds;
    b_cmd_mgr.line[line_index] = ch;

    if (b_cmd_parse_str(&cmd_str_end, ch))
    {
        uint8_t i = 0;
        uint8_t len = strlen(B_CMD_END_STR);

        if (line_index <= len)
        {
            line_index = 0;
            return;
        }

        for (i = 0; i < len; i++)
        {
            b_cmd_mgr.line[line_index - i] = '\0';
        }

        argv[argc++] = &b_cmd_mgr.line[0];
        for (i = 0; i <= line_index - len; i++)
        {
            if (char_in_string((uint8_t *)B_CMD_SPLIT_STR, b_cmd_mgr.line[i]))
            {
                b_cmd_mgr.line[i] = '\0';
                if (!char_in_string((uint8_t *)B_CMD_SPLIT_STR, b_cmd_mgr.line[i + 1]))
                {
                    if (b_cmd_mgr.line[i + 1] != '\0')
                    {
                        argv[argc++] = &b_cmd_mgr.line[i + 1];
                    }
                }
            }
            if (argc > B_CMD_ARGS_NUM_MAX)
            {
                b_cmd_put_str("[Command parameter exceeded!!]\r\n");
                break;
            }
        }
#if (B_CMD_HELP_EN != 0)
        cmds_ptr = &cmds_help;
#endif
        while (cmds_ptr != NULL)
        {
            if (B_CMD_HEAD_STR[0] == '\0')
            {
                pagv = &argv[0];
            }
            else
            {
                if (strcmp(B_CMD_HEAD_STR, argv[0]) == 0)
                {
                    pagv = &argv[1];
                }
            }

            if (strcmp(cmds_ptr->head_name, *pagv) == 0)
            {
#if (B_CMD_VAR_PRINT_EN != 0)
                cmd_print_var_cb(argc, argv);
#endif
                if (cmds_ptr->cb(argc, argv) == B_CMD_RET_FAIL)
                {
                    b_cmd_put_str(B_CMD_EXE_FAIL_STR);
                }
                else
                {
                    b_cmd_put_str(B_CMD_EXE_SUCESS_STR);
                }
                break;
            }
#if (B_CMD_HELP_EN != 0)
            else if (cmds_ptr == &cmds_help)
            {
                cmds_ptr = b_cmd_mgr.cmds;
                continue;
            }
#endif
            cmds_ptr = b_cmd_get_next_cmd(cmds_ptr);
            if (cmds_ptr == NULL)
            {
                b_cmd_put_str("[err cmd] >>>\r\n");
#if (B_CMD_VAR_PRINT_EN != 0)
                cmd_print_var_cb(argc, argv);
#endif
                b_cmd_put_str("\r\n");
            }
        }

        line_index = 0;
    }
    else
    {
        if (line_index < B_CMD_LINE_CHAR_MAX - 1)
        {
            line_index++;
        }
        else
        {
            line_index = 0;
            b_cmd_put_str("[Command buffer exceeded!!]\r\n");
        }
    }
}


void b_cmd_task(void)
{
    uint8_t ch = 0;
    while (cmd_ring_getc(&ch) != 0)
    {
        b_cmd_str_t *p_cmd_str = (b_cmd_str_t *)b_cmd_mgr.strs;
        while (p_cmd_str != NULL)
        {
            if (b_cmd_parse_str(p_cmd_str, ch))
            {
                p_cmd_str->cb();
            }
            p_cmd_str = (b_cmd_str_t *)b_cmd_str_get_next_cmd(p_cmd_str);
        }

        b_cmd_parse_char(ch);
    }
}